package eecs285.proj4.gui;

import eecs285.proj4.gui.Chrono;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Vector;

import static eecs.util.Utility.*;

import javax.imageio.ImageIO;
import javax.swing.*;

/**
 * A custom class that extends the Canvas class to make use of its default
 * implementation as well extends its functionality.
 * 
 * @author Easton Thomas
 * @author Hans Marquard
 * @author Stubbed by Vidal Borromeo
 */
@SuppressWarnings("serial")
public class MarkerboardCanvas extends Canvas implements Runnable, KeyListener,
    MouseListener, MouseMotionListener
{

  // the place in the list that we start crushing
  public static final int crush_begin = 0;
  public static final int crush_end = 10;
  // the allowable size limit of the list
  public static final int crush_limit = 30;
  private Graphics2D gBuf; // 2D Graphics instance for double buffering
  BufferedImage backbuffer = null; // Double Buffer Image
  
  // my Swing timer
  Timer timer;
  // a boolean to synchronize computation and drawing
  boolean computationDone = false;
  // a boolean to ask the treat to stop
  boolean threadStop = false;

  // back color white
  Color backColor = new Color(255, 255, 255);

  // Declare class instances for each type
  MAction action;

  // Holds type of current action
  private MAction.TYPE type;

  // the current buffer of permanent actions
  BufferedImage image;
  
  // Holds array of MActions as an undo buffer
  Vector<MAction> actionList = new Vector<MAction>();

  // Boolean variable to allow paint() to run initial paint jobs
  private Boolean Load = true;

  // private String s =""; // For Text sent to Draw()
  private int height = 600;
  private int width = 800;

  Markerboard parent;

  // ---------------------------------------------------------------------------

  public MarkerboardCanvas(Markerboard parent_)
  {

    super();
    parent = parent_;
    // so ignore System's paint request I will handle them
    setIgnoreRepaint(true);
    this.setSize(width, height);
    setBackground(Color.white);
    addMouseListener(this);
    addMouseMotionListener(this);
    addKeyListener(this);

    image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    // image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB,
    // ColorModel)
    Graphics2D g2d = image.createGraphics();
    // Draw on the image
    g2d.setColor(Color.white);
    g2d.fill(new java.awt.Rectangle(0, 0, width, height));
    g2d.dispose();

    type = MAction.TYPE.FREEDRAW; // Temporary hardcoding until buttons arrive

    // build Chrono that will call me
    Chrono chrono = new Chrono(this);
    // ask the chrono to call me every 30 times a second so every 30 ms (set say
    // 15 for the time it takes to paint)
    timer = new Timer(30, chrono);
    timer.start();
  }

  // ---------------------------------------------------------------------------

  // my own paint method that repaint off line and switch the displayed buffer
  // according to the VBL
  public void myRepaint()
  {
    // computation a lot longer than expected (more than 15ms)... ignore it
    if( !computationDone )
    {
      return;
    }

    // ok doing the repaint on the not showed page
    BufferStrategy strategy = getBufferStrategy();
    Graphics graphics = strategy.getDrawGraphics();
    // erase all what I had
    graphics.setColor(backColor);
    graphics.fillRect(0, 0, this.width, this.height);

    // draw the background
    graphics.drawImage(image, 0, 0, this);

    // draw all children here
    // Acquire lock so actionList can't be changed while drawing.
    synchronized( actionList )
    {
      for (MAction item : actionList)
      {
        item.draw((Graphics2D) graphics);
      }
    }

    if( graphics != null )
      graphics.dispose();
    // show next buffer
    strategy.show();
    // synchronized the blitter page shown
    Toolkit.getDefaultToolkit().sync();
    // ok I can be called again
    computationDone = false;
  }

  private void crush()
  {
    // Copy image to buffered image
    Graphics g = image.createGraphics();
    // Paint the image onto the buffered image
    g.drawImage(image, 0, 0, null);
    // draw the selected actions to the buffer
    synchronized( actionList )
    {
      for (int i = crush_begin; i < crush_end; ++i)
      {
        actionList.get(i).draw((Graphics2D) g);
      }
      // actionList.removeAll(actionList.subList(crush_begin,
      // actionList.size()-1));
      for (int i = crush_begin; i < crush_end; ++i)
      {
        actionList.remove(0);
      }
    }
    g.dispose();
  }

  public byte[] get_image()
  {
    BufferedImage bi = new BufferedImage(image.getWidth(null),image.getHeight(null),
          BufferedImage.TYPE_INT_RGB);
    Graphics2D g2 = bi.createGraphics();
    // Draw img into bi so we can write it to file.
    g2.drawImage(image, 0, 0, null);
    synchronized( actionList )
    {
      for (MAction item : actionList)
      {
        item.draw((Graphics2D) g2);
      }
    }
    g2.dispose();
    // Now bi contains the image and current actions.
   
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    try
    {
      ImageIO.write(bi, "PNG", bytes);
    }
    catch( IOException e )
    {
      e.printStackTrace();
    }
    return bytes.toByteArray();
  }
  
  public void set_image(byte[] bytes)
  {
    try
    {
      image = ImageIO.read(new ByteArrayInputStream(bytes));
      actionList.clear();
    }
    catch( IOException e )
    {
      e.printStackTrace();
    }
  }
  
  public BufferedImage getBufImage()
  {
	  BufferedImage bi = new BufferedImage(image.getWidth(null),image.getHeight(null),
	          BufferedImage.TYPE_INT_RGB);
	    Graphics2D g2 = bi.createGraphics();
	    // Draw img into bi so we can write it to file.
	    g2.drawImage(image, 0, 0, null);
	    synchronized( actionList )
	    {
	      for (MAction item : actionList)
	      {
	        item.draw((Graphics2D) g2);
	      }
	    }
	    g2.dispose();
	    // Now bi contains the image and current actions.
    return bi;
  }

  
  public void undo(MAction action_)
  {
    synchronized( actionList )
    {
      actionList.remove(action_);
    } 
  }

  public void run()
  {
    // loop forever
    for (;;)
    {
      // my father ask me to stop
      if( threadStop )
      {
        // stop the timer called every 15ms
        timer.stop();
        // and exit myself from the run method
        return;
      }
      // test if the computation where already done without a paint
      while( computationDone )
      {
        try
        {
          Thread.sleep(1L); // if it the case sleep a milli
          continue; // try again
        }
        catch( Exception e )
        {
        }
      }
      // repaint();
      // tell myRepaint() that new positions have been calculated
      computationDone = true;
    }
  }

  // ---------------------------------------------------------------------------

  public void paint(Graphics g)
  {

    // First time
    if( backbuffer == null )
    {
      backbuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // create
                                                                                 // image
                                                                                 // to
                                                                                 // buffer
      // drawings too
      gBuf = (Graphics2D) backbuffer.getGraphics(); // get graphics of buffer
      // gBuf.drawImage(img, 0, 0, this); // Write background image to buffer
    }

    // If not first load, call drawing function for appropriate type
    if( Load == false )
    {
      action.draw(gBuf);
    }

    // Set first time flag to false
    Load = false;

    // Draw the buffer image to Canvas
    g.drawImage(backbuffer, 0, 0, this);

  }

  // --------------------------------------------------------------------------

  public void setType(MAction.TYPE type_)
  {
    type = type_;
  }

  // ---------------------------------------------------------------------------

  public void clearMarkerboard(MAction.TYPE type_)
  {
    type = type_;
  }

  //---------------------------------------------------------------------------

  public Color getDrawColor()
  {
    switch (parent.colorChooserComboBox.getSelectedIndex())
    {
      case 0:
      {
        return Color.BLACK;
      }
      case 1:
      {
        return Color.PINK;
      }
      case 2:
      {
        return Color.getHSBColor((float) .02, 1, 1);
      }
      case 3:
      {
        return Color.getHSBColor((float) .1, 1, 1);
      }
      case 4:
      {
        return Color.getHSBColor((float) .17, 1, 1);
      }
      case 5:
      {
        return Color.getHSBColor((float) .3, 1, 1);
      }
      case 6:
      {
        return Color.getHSBColor((float) .5, 1, 1);
      }
      case 7:
      {
        return Color.getHSBColor((float) .6, 1, 1);
      }
      case 8:
      {
        return Color.getHSBColor((float) .8, 1, 1);
      }
      case 9:
      {
        return Color.GRAY;
      }
      default:
      {
        return Color.BLACK;
      }
    }
  }

  // ---------------------------------------------------------------------------

  public void mousePressed(MouseEvent e)
  {
    textDone();
    if( e.getX() < width && e.getY() < height ) // check if mouse is in Canvas
    // area
    {
      switch (type)
      {
        case CIRCLE:
        {
          action = new Circle(new Point(e.getX(), e.getY()), new Point(
              e.getX(), e.getY()));
          action.mine = true;
          action.setColor(getDrawColor());
          action.setStroke(Integer.parseInt(parent.strokeChooserTextField
              .getText()));
          break;
        }
        case RECTANGLE:
        {
          action = new Rectangle(new Point(e.getX(), e.getY()), new Point(
              e.getX(), e.getY()));
          action.mine = true;
          action.setColor(getDrawColor());
          action.setStroke(Integer.parseInt(parent.strokeChooserTextField
              .getText()));
          break;
        }
        case LINE:
        {
          action = new Line(new Point(e.getX(), e.getY()), new Point(e.getX(),
              e.getY()));
          action.mine = true;
          action.setColor(getDrawColor());
          action.setStroke(Integer.parseInt(parent.strokeChooserTextField
              .getText()));
          break;
        }
        case FREEDRAW:
        {
          action = new FreeDraw(new Point(e.getX(), e.getY()));
          action.mine = true;
          action.setColor(getDrawColor());
          action.setStroke(Integer.parseInt(parent.strokeChooserTextField
              .getText()));
          break;
        }
        case ERASE:
        {
          action = new Erase(new Point(e.getX(), e.getY()));
          action.mine = true;
          action.setColor(getDrawColor());
          action.setStroke(Integer.parseInt(parent.strokeChooserTextField
              .getText()));
          break;
        }
        case TEXT:
        {
          action = new Text(new String(""), new Point(e.getX(), e.getY()));
          action.mine = true;
          action.setColor(getDrawColor());
          action.setFontSize(Integer.parseInt(parent.strokeChooserTextField
              .getText()));
          action.setFontByName("Helvetica");
          action.setStroke(Integer.parseInt(parent.strokeChooserTextField
              .getText()));
          // action.setStroke(Integer.parseInt(parent.strokeChooserTextField.getText()));
          break;
        }
        default:
        {
          break;
        }
      }

      addToActionList(action); // synchronously add to actionList
      
    }
  }

  // ---------------------------------------------------------------------------

  public void mouseReleased(MouseEvent e)
  {

    if( e.getX() < width && e.getY() < height ) // check if mouse is in Canvas
    // area
    {
      action.setEnd(new Point(e.getX(), e.getY()));
      
      if(action.type == MAction.TYPE.TEXT)//text entries are unlike any other action in that
      {                                   // they release immediately and we dont want to nullify that
        return;
      }
      //remove from queue and replace to help ensure ordering
      removeFromActionList(action);
      addToActionList(action);
      // broadcasting here
      try
      {
        parent.connection.broadcast(Markerboard.KEY_DRAW_ACTION, action);
      }
      catch( IOException e1 )
      {
        // TODO Auto-generated catch block
        // e1.printStackTrace();
        debug("Could not send action.");
      }
      finally
      {
        action = null;
      }

    }
    else if(action != null)
    {
      //removeFromActionList(action); //taken out at Easton's request
      action = null;
    }
  }

  // ---------------------------------------------------------------------------

  public void mouseDragged(MouseEvent e)
  {
    action.add_point(new Point(e.getX(), e.getY()));
  }

  // ---------------------------------------------------------------------------

  public void keyTyped(KeyEvent e)
  {
    // get character
    char c = e.getKeyChar();
    if(action != null)
    {
      if( c != KeyEvent.CHAR_UNDEFINED )
      {
        if( c == KeyEvent.VK_BACK_SPACE )
        {
          String str = new String(action.getText());
          str = str.substring(0, Math.max(0, str.length() - 1));
          action.setText(str);
        }
        else if(c == KeyEvent.VK_ENTER)
        {
          
            if(action.type == MAction.TYPE.TEXT)
            {
              textDone();
            }
  
        }
        else
        {
          try
          {
            String str = new String(action.getText());
            str = str + c; // append new character to string
            action.setText(str);
          }
          catch( NullPointerException ex )
          {
             System.out.println(ex.getMessage());
             ex.printStackTrace();
          }
        }
      }
      e.consume();
    }
  }
  
  public void textDone()
  {
    if(action == null)
      return;
    if(action.type != MAction.TYPE.TEXT)
      return;
  //remove from queue and replace to help ensure ordering
    removeFromActionList(action);
    if(action.getText().length() > 0) //if the user typed anything
    {
      addToActionList(action);
      // broadcasting here
      try
      {
        parent.connection.broadcast(Markerboard.KEY_DRAW_ACTION, action);
      }
      catch( IOException e1 )
      {
        // TODO Auto-generated catch block
        // e1.printStackTrace();
        debug("Could not send action.");
      }
      finally
      {
        action = null;
      }
    }
    else
    {
      action = null;
    }
  }

  // ---------------------------------------------------------------------------

  public void mouseMoved(MouseEvent e)
  {

  }

  public void mouseClicked(MouseEvent e)
  {

  }

  public void mouseEntered(MouseEvent e)
  {

  }

  public void mouseExited(MouseEvent e)
  {

  }

  public void keyPressed(KeyEvent e)
  {

  }

  public void keyReleased(KeyEvent e)
  {

  }

  public void addToActionList(MAction action_)
  {
    
    synchronized( parent.outstandingAnnounce )
    {
      // if there is an outstanding sync request
      // drawing will cancel it.
      parent.outstandingAnnounce = false;
    }// sync

    synchronized( actionList )
    {
      actionList.add(action_);
      if( actionList.size() > crush_limit )
      {
        crush();
      }
    }
  }
  
  public void removeFromActionList(MAction action_)
  {
    synchronized( actionList )
    {
      actionList.remove(action_);
    }
  }


  // public void addNewAction(MAction action_)
  // {
  // synchronized(actionList)
  // {
  // actionList.add(action_);
  // }
  // action = action_;
  //
  // }

}// MarkerboardCanvas
